/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 * Description: main file
 */
#include <sys/time.h>
#include <unistd.h>
#include <cstdlib>
#include <opencv2/core/core.hpp>
#include <opencv2/core/utils/filesystem.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <vector>
#include <csignal>
#include <memory>
#include <boost/algorithm/string.hpp>
#include "pcd_sender.h"
#include "ara/rm/rm.h"

#include "decoded_mbuf_image_sender.h"
#include "decoded_mbuf_video_sender.h"
#include "decoded_mbuf_yuv_sender.h"
#include "image_mbuf_image_sender.h"
#include "image_mbuf_yuv_sender.h"
#include "image_mbuf_video_sender.h"
#include "util.h"

using namespace std;
using namespace cv;

int32_t g_stopFlag = 0;

inline bool IsInstanceIdAvialable(std::string fileType, int32_t id)
{
    bool idIsAvailable = false;
    if (fileType == "pcd") {
        // 3为lidar的InstanceId最小值 7为lidar的InstanceId最大值
        if (id >= 3 && id <= 7) {
            idIsAvailable = true;
            return idIsAvailable;
        }
        std::cout << "current " << fileType << " instanceId is " << id <<
            ",instanceId error,available value is [3,7]" << std::endl;
        return idIsAvailable;
    }
    // 21为camera的InstanceId最小值 35为camera的InstanceId最大值
    if (id >= 21 && id <= 35 && g_comPara.transMode != "image") {
        idIsAvailable = true;
        return idIsAvailable;
    }
    // 1021 is min of camera InstancdID, 1034 is max of camera InstanceID
    if (id >= CN1021 && id <= CN1034 && g_comPara.transMode == "image") {
        idIsAvailable = true;
        return idIsAvailable;
    }
    std::cout << "current " << fileType << " instanceId is " << id << ",instanceId error！" << std::endl;
    std::cout << "cameraDecodedMbufEvent available instanceId avaiable is [21,35]." << std::endl;
    std::cout << "cameraImageMbufEvent available instanceId avaiable is [1021,1034]." << std::endl;
    return idIsAvailable;
}

bool IsDateTimeAvailabe(string &dateTime)
{
    vector<string> dateStringVec;
    boost::split(dateStringVec, dateTime, boost::is_any_of(" "), boost::token_compress_on);
    if (dateStringVec.size() != CN2) {
        std::cout << "the input datetime format is error,usage \'2022-02-18 10:09:08\'" << std::endl;
        return false;
    }
    vector<string> timeStringVec;
    boost::split(timeStringVec, dateStringVec[1], boost::is_any_of(":"), boost::token_compress_on);
    if (timeStringVec.size() != CN3) {
        std::cout << "the input datetime format is error,usage \'2022-02-18 10:09:08\'" << std::endl;
        return false;
    }
    uint32_t hour = atoi(timeStringVec[0].c_str());
    uint32_t minutes = atoi(timeStringVec[1].c_str());
    uint32_t seconds = atoi(timeStringVec[2].c_str());
    if (hour >= CN24 || minutes >= CN60 || seconds >= CN60) {
        std::cout << "the input datetime format is error,usage \'2022-02-18 10:09:08\'" << std::endl;
        return false;
    }
    return true;
}

int MainF2Check()
{
    if (g_comPara.fileType == "") {
        std::cout << "'-f File type' can not be empty!" << std::endl;
        return 1;
    } else {
        if (g_comPara.fileType != "jpg" && g_comPara.fileType != "jpeg" && g_comPara.fileType != "yuv" &&
            g_comPara.fileType != "h264" && g_comPara.fileType != "h265" && g_comPara.fileType != "pcd") {
            std::cout << "-f File type is error: " << g_comPara.fileType << std::endl;
            return 1;
        }
    }
    if (g_comPara.configFile == "") {
        std::cout << "'-c Config file' can not be empty!" << std::endl;
        return 1;
    }
    // 校验时间
    std::string timeSample = "2022-02-18 10:09:08";
    try {
        if (!g_comPara.time.empty()) {
            if (g_comPara.time.length() != timeSample.length()) {
                std::cout << "the input datetime format is error,usage \'" << timeSample << "\'" << std::endl;
                return -1;
            }
            if (!IsDateTimeAvailabe(g_comPara.time)) {
                return -1;
            }
            boost::gregorian::date dateTime = boost::gregorian::from_string(g_comPara.time);
        }
    } catch (std::exception &e) {
        std::cout << "the input datetime format is error,usage \'" << timeSample << "\'" << std::endl;
        return -1;
    }
    // 配置文件不存在则退出
    if (!ConfigMangage::FileExist(g_comPara.configFile)) {
        std::cout << g_comPara.configFile << " file is not exist!" << std::endl;
        return 1;
    }
    if (g_comPara.frequency != "") {
        int32_t frequency = atoi(g_comPara.frequency.c_str());
        // 100 is max frequency
        if (frequency <= 0 || frequency > CN100) {
            std::cout << "-r frequency must be (0 < frequency <= 100)" << std::endl;
            return 1;
        }
    }
    if (g_comPara.transMode != "") {
        if (g_comPara.transMode != "decoded" && g_comPara.transMode != "image" &&
            g_comPara.transMode != "standard" && g_comPara.transMode != "simple") {
            std::cout << "-m transfer mode is error:" << g_comPara.transMode << std::endl;
        }
    }
    return 0;
}

int MainF4RegisterV1(
    vector<shared_ptr<DecodedMbufImageSender>> &mbufSenders,
    vector<shared_ptr<DecodedMbufVideoSender>> &mbufVideoSenders,
    vector<shared_ptr<DecodedMbufYuvSender>> &mbufYuvSenders,
    vector<shared_ptr<PcdSender>> &pcdSenders)
{
    for (auto &send : mbufSenders) {
        send->RegisterSender();
        // 2880 is mbuf video width,  1856 is mbuf video height, 3 is mbuf video number, 20 is mbuf pool number
        if (send->BufCreatePool(CN2880 * CN1856 * CN3, CN20) == 0) {
            std::cout << "INFO: send->BufCreatePool succeed one!" << std::endl;
        } else {
            std::cout << "ERROR: send->BufCreatePool failed!" << std::endl;
            return -1;
        }
    }
    for (auto &videoSend : mbufVideoSenders) {
        videoSend->RegisterSender();
        if (videoSend->BufCreatePool(CN2880 * CN1856 * CN3, CN20) == 0) {
            std::cout << "INFO: send->BufCreatePool succeed one!" << std::endl;
        } else {
            std::cout << "ERROR: send->BufCreatePool failed!" << std::endl;
            return -1;
        }
    }
    for (auto &yuvSend : mbufYuvSenders) {
        yuvSend->RegisterSender();
        if (yuvSend->BufCreatePool(CN2880 * CN1856 * CN3, CN20) == 0) {
            std::cout << "INFO: send->BufCreatePool succeed one!" << std::endl;
        } else {
            std::cout << "ERROR: send->BufCreatePool failed!" << std::endl;
            return -1;
        }
    }
    for (auto &pcdSend : pcdSenders) {
        pcdSend->RegisterSender();
    }

    usleep(CN1000000);
    vector<thread> pool;
    for (auto &send : mbufSenders) {
        pool.push_back(thread(&DecodedMbufImageSender::FillImageAndSend, send, "", 0, &g_stopFlag));
    }
    for (auto &videoSend : mbufVideoSenders) {
        videoSend->VdecDecoded();
    }
    for (auto &yuvSend : mbufYuvSenders) {
        pool.push_back(thread(&DecodedMbufYuvSender::FillImageAndSend, yuvSend, "", 0, &g_stopFlag));
    }
    for (auto &pcdSend : pcdSenders) {
        pool.push_back(thread(&PcdSender::FillImageAndSend, pcdSend, "", 0, &g_stopFlag));
    }
    for (auto &t : pool) {
        if (t.joinable()) {
            t.join();
        }
    }
    return 0;
}

int MainF4RegisterV2(
    vector<shared_ptr<ImageMbufImageSender>> &imageMbufSenders,
    vector<shared_ptr<ImageMbufYuvSender>> &imageMbufYuvSenders,
    vector<shared_ptr<ImageMbufVideoSender>> &imageMbufVideoSenders)
{
    for (auto &imageSend : imageMbufSenders) {
        imageSend->RegisterSender();
        // 3 is mbuf video number, 2 is get half, 40 is mbuf pool number
        if (imageSend->BufCreatePool(imageSend->imgSize.width * imageSend->imgSize.height * 3 / 2, 40) == 0) {
            std::cout << "INFO: send->BufCreatePool succeed one!" << std::endl;
        } else {
            std::cout << "ERROR: send->BufCreatePool failed!" << std::endl;
            return -1;
        }
    }
    for (auto &imageYuvSend : imageMbufYuvSenders) {
        imageYuvSend->RegisterSender();
        if (imageYuvSend->BufCreatePool(
            imageYuvSend->imgSize.width * imageYuvSend->imgSize.height * CN3 / CN2, CN40) == 0) {
            std::cout << "INFO: send->BufCreatePool succeed one!" << std::endl;
        } else {
            std::cout << "ERROR: send->BufCreatePool failed!" << std::endl;
            return -1;
        }
    }
    for (auto &imageVideoSend : imageMbufVideoSenders) {
        imageVideoSend->RegisterSender();
        if (imageVideoSend->BufCreatePool(
            imageVideoSend->imgSize.width * imageVideoSend->imgSize.height * CN3 / CN2, CN40) == 0) {
            std::cout << "INFO: send->BufCreatePool succeed one!" << std::endl;
        } else {
            std::cout << "ERROR: send->BufCreatePool failed!" << std::endl;
            return -1;
        }
    }

    usleep(CN1000000);
    vector<thread> pool;
    for (auto &imageSend : imageMbufSenders) {
        pool.push_back(thread(&ImageMbufImageSender::FillImageAndSend, imageSend, "", 0, &g_stopFlag));
    }
    for (auto &imageYuvSend : imageMbufYuvSenders) {
        pool.push_back(thread(&ImageMbufYuvSender::FillImageAndSend, imageYuvSend, "", 0, &g_stopFlag));
    }
    for (auto &imageVideoSend : imageMbufVideoSenders) {
        imageVideoSend->VdecDecoded();
    }
    for (auto &t : pool) {
        if (t.joinable()) {
            t.join();
        }
    }
    return 0;
}

void MainF3ConfigImage(std::vector<Config> &configs,
    vector<shared_ptr<PcdSender>> &pcdSenders,
    vector<shared_ptr<ImageMbufImageSender>> &imageMbufSenders,
    vector<shared_ptr<ImageMbufYuvSender>> &imageMbufYuvSenders,
    vector<shared_ptr<ImageMbufVideoSender>> &imageMbufVideoSenders)
{
    for (auto &conf : configs) {
        std::vector<std::string> files;
        int32_t fileCnt = getFilesBySort(conf.dir, files, g_comPara.fileType);
        if (fileCnt == 0) {
            std::cout << "dir : " << conf.dir << " has no " << g_comPara.fileType << " file" << std::endl;
            continue;
        }
        // 校验instanceId
        if (!IsInstanceIdAvialable(g_comPara.fileType, conf.instanceId)) {
            continue;
        }
        if (g_comPara.fileType == "jpg" || g_comPara.fileType == "jpeg") {
            imageMbufSenders.push_back(make_shared<ImageMbufImageSender>(
                conf.instanceId, Size{conf.width, conf.height}, conf.dir));
        }
        if (g_comPara.fileType == "h264" || g_comPara.fileType == "h265") {
            imageMbufVideoSenders.push_back(make_shared<ImageMbufVideoSender>(
                conf.instanceId, Size{conf.width, conf.height}, conf.dir));
        }
        if (g_comPara.fileType == "yuv") {
            imageMbufYuvSenders.push_back(make_shared<ImageMbufYuvSender>(
                conf.instanceId, Size{conf.width, conf.height}, conf.dir));
        }
        if (g_comPara.fileType == "pcd") {
            pcdSenders.push_back(make_shared<PcdSender>(
                conf.instanceId, Size{conf.width, conf.height}, conf.dir));
        }
    }
}

void MainF3ConfigElse(std::vector<Config> &configs,
    vector<shared_ptr<DecodedMbufImageSender>> &mbufSenders,
    vector<shared_ptr<DecodedMbufVideoSender>> &mbufVideoSenders,
    vector<shared_ptr<DecodedMbufYuvSender>> &mbufYuvSenders,
    vector<shared_ptr<PcdSender>> &pcdSenders)
{
    for (auto &conf : configs) {
        std::vector<std::string> files;
        int32_t fileCnt = getFilesBySort(conf.dir, files, g_comPara.fileType);
        if (fileCnt == 0) {
            std::cout << "dir : " << conf.dir << " has no " << g_comPara.fileType << " file" << std::endl;
            continue;
        }
        // 校验instanceId
        if (!IsInstanceIdAvialable(g_comPara.fileType, conf.instanceId)) {
            continue;
        }
        if (g_comPara.fileType == "jpg" || g_comPara.fileType == "jpeg") {
            mbufSenders.push_back(make_shared<DecodedMbufImageSender>(
                conf.instanceId, Size{conf.width, conf.height}, conf.dir));
        }
        if (g_comPara.fileType == "h264" || g_comPara.fileType == "h265") {
            mbufVideoSenders.push_back(make_shared<DecodedMbufVideoSender>(
                conf.instanceId, Size{conf.width, conf.height}, conf.dir));
        }
        if (g_comPara.fileType == "yuv") {
            mbufYuvSenders.push_back(make_shared<DecodedMbufYuvSender>(
                conf.instanceId, Size{conf.width, conf.height}, conf.dir));
        }
        if (g_comPara.fileType == "pcd") {
            pcdSenders.push_back(make_shared<PcdSender>(
                conf.instanceId, Size{conf.width, conf.height}, conf.dir));
        }
    }
}

int MainF3Config()
{
    int ret = 0;

    ConfigMangage configManage{g_comPara.configFile};
    configManage.Parse();
    cout << "parse finished. [main]" << endl;
    configManage.Show();
    if (!configManage.CheckConfig()) {
        cerr << "Config check failed." << endl;
        return 1;
    }
    auto configs = configManage.GetConfig();
    if (!ara::rm::RegisterHisiResource()) {
        std::cerr << "Register process to RM failed! Check if RM has been started." << std::endl;
        return -1;
    }

    vector<shared_ptr<DecodedMbufImageSender>> mbufSenders;
    vector<shared_ptr<DecodedMbufVideoSender>> mbufVideoSenders;
    vector<shared_ptr<DecodedMbufYuvSender>> mbufYuvSenders;
    vector<shared_ptr<PcdSender>> pcdSenders;
    vector<shared_ptr<ImageMbufImageSender>> imageMbufSenders;
    vector<shared_ptr<ImageMbufYuvSender>> imageMbufYuvSenders;
    vector<shared_ptr<ImageMbufVideoSender>> imageMbufVideoSenders;

    if (g_comPara.transMode == "image") {
        MainF3ConfigImage(configs, pcdSenders, imageMbufSenders, imageMbufYuvSenders, imageMbufVideoSenders);
    } else {
        MainF3ConfigElse(configs, mbufSenders, mbufVideoSenders, mbufYuvSenders, pcdSenders);
    }

    ret = MainF4RegisterV1(mbufSenders, mbufVideoSenders, mbufYuvSenders, pcdSenders);
    if (ret != 0) {
        return ret;
    }
    ret = MainF4RegisterV2(imageMbufSenders, imageMbufYuvSenders, imageMbufVideoSenders);
    if (ret != 0) {
        return ret;
    }
    return 0;
}

void Print4H()
{
    printf("--usage: \n");
    printf("./local_sensor_simulation -f [fileType] -c [configFile] -m [transMode] \n");
    printf("for example: ./local_sensor_simulation -f jpg -c image_config.yaml -m decoded -r 30 \n");
    printf("\n");
    printf("--option: \n");
    printf("  -f : [jpg/jpeg/yuv/h264/h265/pcd], requirement \n");
    printf("  -c : [configFile], requirement \n");
    printf("  -m : [decoded/image/simple/standard], option \n");
    printf("  -t : [yyyy-MM-dd HH:mm:ss], option \n");
    printf("  -r : [hz], option \n");
    printf("  -l , for loop, option \n");
    printf("  -h , for help, option \n");
}

int main(int32_t argc, char *argv[])
{
    int32_t ch;
    while ((ch = getopt(argc, argv, "c:f:hlm:r:t:")) != -1) {
        switch (ch) {
            case 'c':
                g_comPara.configFile = optarg;
                break;
            case 'f':
                g_comPara.fileType = optarg;
                break;
            case 'h':
                Print4H();
                return 0;
            case 'l':
                g_comPara.loop = true;
                break;
            case 'm':
                g_comPara.transMode = optarg;
                break;
            case 'r':
                g_comPara.frequency = optarg;
                break;
            case 't':
                g_comPara.time = optarg;
                break;
            case '?':
                printf("miss para!\n");
                break;
            case ':':
                printf("invalid para! please input '-h' for help\n");
                break;
            default:
                break;
        }
    }

    int ret = MainF2Check();
    if (ret != 0) {
        return ret;
    }
    ret = MainF3Config();
    if (ret != 0) {
        return ret;
    }

    return 0;
}
